﻿Imports System.Reflection
Imports Magic.Core
Public Class HaloTag
    Public Shared Function FromTagFile(ByVal name As String, fourcc As String, index As TagIndex) As HaloTag
        Dim filename As String = String.Format("{0}\{1}.{2}", appSettings.XboxTagFolder, name, tagExtensions.Item(fourcc))
        If IO.File.Exists(filename) Then
            Return FromTagFile(filename, index)
        Else
            filename = String.Format("{0}\{1}.{2}", appSettings.CETagFolder, name, tagExtensions.Item(fourcc))
            If IO.File.Exists(filename) Then
                Return FromTagFile(filename, index)
            End If
        End If
        'TODO: Should throw exception.
        Return Nothing
    End Function

    Public Shared Function FromTagFile(ByVal filename As String, index As TagIndex) As HaloTag
        'Open File and stream it to byte array.
        Dim eReader As New Magic.Core.EndianReader(New IO.FileStream(filename, IO.FileMode.Open), ByteOrder.BigEndian)
        Dim temp As Byte() = eReader.ReadBytes(eReader.Length)
        eReader.Close()

        'Open byte array as memory stream and stream the tag. Reading in memory is alot faster than disk IO.
        eReader = New Magic.Core.EndianReader(New IO.MemoryStream(temp), ByteOrder.BigEndian)
        eReader.Position = 36

        'Read FourCC from tag file header
        Dim tagFourCC As String = eReader.ReadString(4, False)
        eReader.Position = 64

        'Instantiate new class based on tag's FourCC
        Dim nTag As HaloTag = Activator.CreateInstance(tagTypes.Item(tagFourCC))

        'Get class type and all fields associated.
        Dim definitions As List(Of TagDefinition) = tagDefinitions.Item(tagFourCC)

        'Iterate through tag and populate simple fields (header values)
        Dim complexList As New List(Of Integer)
        For i As Integer = 0 To definitions.Count - 1
            Dim complex As Boolean = False
            definitions(i).Field.SetValue(nTag, ReadHeaderValues(definitions(i), eReader, index, complex))
            If complex Then complexList.Add(i)
        Next i

        'Iterate through tag and populate complex structures

        For i As Integer = 0 To complexList.Count - 1
            definitions(complexList(i)).Field.SetValue(nTag, ReadComplexValues(definitions(complexList(i)), eReader, definitions(complexList(i)).Field.GetValue(nTag), index))
        Next i

        'Close stream
        eReader.Close()

        'Return tag object
        Return nTag
    End Function

    Private Shared Function ReadHeaderValues(tagFieldInfo As TagDefinition, eReader As Magic.Core.EndianReader, index As TagIndex, ByRef complex As Boolean)

        Select Case tagFieldInfo.FieldType
            Case TagFieldAttribute.FieldType.String
                Return eReader.ReadString(32)
            Case TagFieldAttribute.FieldType.Float1
                Return eReader.ReadSingle
            Case TagFieldAttribute.FieldType.Float2, TagFieldAttribute.FieldType.Float3, TagFieldAttribute.FieldType.Float4
                Return eReader.ReadSingleArray(tagFieldInfo.FieldSize)
            Case TagFieldAttribute.FieldType.Int1, TagFieldAttribute.FieldType.SelfReference, TagFieldAttribute.FieldType.DWordFlags
                Return eReader.ReadInt32
            Case TagFieldAttribute.FieldType.Int2, TagFieldAttribute.FieldType.Int3, TagFieldAttribute.FieldType.Int4, TagFieldAttribute.FieldType.ModelIndexBlock, TagFieldAttribute.FieldType.ModelVertexBlock, TagFieldAttribute.FieldType.BitmapDataBlock
                Return eReader.ReadInt32Array(tagFieldInfo.FieldSize)
            Case TagFieldAttribute.FieldType.Short1, TagFieldAttribute.FieldType.WordFlags, TagFieldAttribute.FieldType.Enum
                Return eReader.ReadInt16
            Case TagFieldAttribute.FieldType.Short2, TagFieldAttribute.FieldType.Short3, TagFieldAttribute.FieldType.Short4
                Return eReader.ReadInt16Array(tagFieldInfo.FieldSize)
            Case TagFieldAttribute.FieldType.Char, TagFieldAttribute.FieldType.CharFlags
                Return eReader.ReadByte
            Case TagFieldAttribute.FieldType.Padding
                Return eReader.ReadBytes(tagFieldInfo.FieldSize)
            Case TagFieldAttribute.FieldType.TagReference
                complex = True
                Dim tReference As New TAG_REFERENCE
                tReference.Read(eReader)
                Return tReference
            Case TagFieldAttribute.FieldType.Prestructure, TagFieldAttribute.FieldType.ScriptData, TagFieldAttribute.FieldType.BspVertexDataHeader, TagFieldAttribute.FieldType.BspCompressedVertexBlock, TagFieldAttribute.FieldType.BspUncompressedVertexBlock, TagFieldAttribute.FieldType.BitmapPixelData, TagFieldAttribute.FieldType.SoundDataBlock
                complex = True
                Dim dContainer As New DATA_CONTAINER
                dContainer.Read(eReader)
                Return dContainer
            Case TagFieldAttribute.FieldType.Block
                complex = True
                Dim blockList As IList = Activator.CreateInstance(tagFieldInfo.Field.FieldType)
                Dim listType As Type = tagFieldInfo.ChildType

                Dim blockCount As Integer = eReader.ReadInt32

                For i As Integer = 0 To blockCount - 1
                    blockList.Add(Activator.CreateInstance(listType))
                Next
                eReader.Position += 8
                Return blockList
            Case Else
                Return Nothing
        End Select
    End Function

    Private Shared Function ReadComplexValues(tagFieldInfo As TagDefinition, eReader As Magic.Core.EndianReader, complexStruct As Object, index As TagIndex)

        Select Case tagFieldInfo.FieldType
            Case TagFieldAttribute.FieldType.TagReference
                Dim tReference As TAG_REFERENCE = complexStruct
                If tReference.ReadRaw(eReader) Then
                    tReference.Reference = index.GetDependencyTag(tReference.Filename, tReference.FourCC)
                End If
                Return tReference
            Case TagFieldAttribute.FieldType.Prestructure, TagFieldAttribute.FieldType.ScriptData, TagFieldAttribute.FieldType.BspCompressedVertexBlock, TagFieldAttribute.FieldType.BspUncompressedVertexBlock, TagFieldAttribute.FieldType.BitmapPixelData, TagFieldAttribute.FieldType.SoundDataBlock
                Dim dContainer As DATA_CONTAINER = complexStruct
                dContainer.ReadRaw(eReader)
                Return dContainer
            Case TagFieldAttribute.FieldType.Block
                Dim blockList As IList = complexStruct
                Dim tagType As Type = tagFieldInfo.ChildType

                Dim complexList As New List(Of Integer)
                For f As Integer = 0 To blockList.Count - 1
                    For i As Integer = 0 To tagFieldInfo.Children.Count - 1
                        Dim complex As Boolean = False
                        tagFieldInfo.Children(i).Field.SetValue(blockList(f), ReadHeaderValues(tagFieldInfo.Children(i), eReader, index, complex))
                        If complex And f = 0 Then
                            complexList.Add(i)
                        End If
                    Next i
                Next
                For f As Integer = 0 To blockList.Count - 1
                    For i As Integer = 0 To complexList.Count - 1
                        tagFieldInfo.Children(complexList(i)).Field.SetValue(blockList(f), ReadComplexValues(tagFieldInfo.Children(complexList(i)), eReader, tagFieldInfo.Children(complexList(i)).Field.GetValue(blockList(f)), index))
                    Next
                Next
                Return blockList
            Case Else
                Return complexStruct
        End Select
    End Function

    Public Sub ToTagFile(filename As String)
        'Get tag information
        Dim tagAttrib As TagAttribute = Me.GetType.GetCustomAttribute(Of TagAttribute)

        'Get class type and all fields associated.
        Dim definitions As List(Of TagDefinition) = tagDefinitions.Item(tagAttrib.FourCC)

        'Open Big Endian writer
        Dim eW As New Magic.Core.EndianWriter(New IO.FileStream(filename, IO.FileMode.Create), Magic.Core.ByteOrder.BigEndian)

        'Write Header
        '   Zeros
        eW.Position += 36
        '   FourCC
        eW.WriteFourCC(tagAttrib.FourCC)
        eW.Write(0)
        '   Header Length?
        eW.Write(64)
        '   Zeros
        eW.Write(0)
        eW.Write(0)
        '   Version
        eW.Write(tagAttrib.Version)
        '   255 Short
        eW.Write(255S)
        '   'BLAM' Motherfucker!
        eW.Write(&H626C616D)


        'Iterate through tag and write simple fields (header values)
        Dim complexList As New List(Of Integer)
        For i As Integer = 0 To definitions.Count - 1
            Dim complex As Boolean = False
            WriteHeaderValues(definitions(i), eW, Me, complex)
            If complex Then complexList.Add(i)
        Next i

        'Iterate through tag and write complex structures
        For i As Integer = 0 To complexList.Count - 1
            WriteComplexValues(definitions(complexList(i)), eW, Me)
        Next i

        'Close writer
        eW.Close()
    End Sub

    Private Sub WriteHeaderValues(tagFieldInfo As TagDefinition, eWriter As Magic.Core.EndianWriter, block As Object, ByRef complex As Boolean)
        Select Case tagFieldInfo.FieldType
            Case TagFieldAttribute.FieldType.String
                eWriter.Write(tagFieldInfo.Field.GetValue(block))
            Case TagFieldAttribute.FieldType.Float1
                eWriter.Write(tagFieldInfo.Field.GetValue(block))
            Case TagFieldAttribute.FieldType.Float2, TagFieldAttribute.FieldType.Float3, TagFieldAttribute.FieldType.Float4
                eWriter.WriteArray(tagFieldInfo.Field.GetValue(block))
            Case TagFieldAttribute.FieldType.Int1, TagFieldAttribute.FieldType.SelfReference, TagFieldAttribute.FieldType.DWordFlags
                eWriter.Write(tagFieldInfo.Field.GetValue(block))
            Case TagFieldAttribute.FieldType.Int2, TagFieldAttribute.FieldType.Int3, TagFieldAttribute.FieldType.Int4, TagFieldAttribute.FieldType.ModelIndexBlock, TagFieldAttribute.FieldType.ModelVertexBlock, TagFieldAttribute.FieldType.BitmapDataBlock
                eWriter.WriteArray(tagFieldInfo.Field.GetValue(block))
            Case TagFieldAttribute.FieldType.Short1, TagFieldAttribute.FieldType.WordFlags, TagFieldAttribute.FieldType.Enum
                eWriter.Write(tagFieldInfo.Field.GetValue(block))
            Case TagFieldAttribute.FieldType.Short2, TagFieldAttribute.FieldType.Short3, TagFieldAttribute.FieldType.Short4
                eWriter.WriteArray(tagFieldInfo.Field.GetValue(block))
            Case TagFieldAttribute.FieldType.Char, TagFieldAttribute.FieldType.CharFlags
                eWriter.Write(tagFieldInfo.Field.GetValue(block))
            Case TagFieldAttribute.FieldType.Padding
                eWriter.Write(tagFieldInfo.Field.GetValue(block))
            Case TagFieldAttribute.FieldType.TagReference
                complex = True
                Dim tReference As TAG_REFERENCE = tagFieldInfo.Field.GetValue(block)
                tReference.Write(eWriter)
            Case TagFieldAttribute.FieldType.Prestructure, TagFieldAttribute.FieldType.ScriptData, TagFieldAttribute.FieldType.BspVertexDataHeader, TagFieldAttribute.FieldType.BspCompressedVertexBlock, TagFieldAttribute.FieldType.BspUncompressedVertexBlock, TagFieldAttribute.FieldType.BitmapPixelData, TagFieldAttribute.FieldType.SoundDataBlock
                complex = True
                Dim dContainer As DATA_CONTAINER = tagFieldInfo.Field.GetValue(block)
                dContainer.Write(eWriter)
            Case TagFieldAttribute.FieldType.Block
                complex = True
                Dim blockList As IList = tagFieldInfo.Field.GetValue(block)
                eWriter.Write(blockList.Count)
                eWriter.Position += 8
        End Select
    End Sub

    Private Sub WriteComplexValues(tagFieldInfo As TagDefinition, eWriter As Magic.Core.EndianWriter, complexStruct As Object)

        Select Case tagFieldInfo.FieldType
            Case TagFieldAttribute.FieldType.TagReference
                Dim tReference As TAG_REFERENCE = tagFieldInfo.Field.GetValue(complexStruct)
                tReference.WriteRaw(eWriter)
            Case TagFieldAttribute.FieldType.Prestructure, TagFieldAttribute.FieldType.ScriptData, TagFieldAttribute.FieldType.BspCompressedVertexBlock, TagFieldAttribute.FieldType.BspUncompressedVertexBlock, TagFieldAttribute.FieldType.BitmapPixelData, TagFieldAttribute.FieldType.SoundDataBlock
                Dim dContainer As DATA_CONTAINER = tagFieldInfo.Field.GetValue(complexStruct)
                dContainer.WriteRaw(eWriter)

            Case TagFieldAttribute.FieldType.Block
                Dim blockList As IList = tagFieldInfo.Field.GetValue(complexStruct)
                Dim tagType As Type = tagFieldInfo.ChildType

                Dim complexList As New List(Of Integer)
                For f As Integer = 0 To blockList.Count - 1
                    For i As Integer = 0 To tagFieldInfo.Children.Count - 1
                        Dim complex As Boolean = False
                        WriteHeaderValues(tagFieldInfo.Children(i), eWriter, blockList(f), complex)
                        If complex And f = 0 Then
                            complexList.Add(i)
                        End If
                    Next i
                Next
                For f As Integer = 0 To blockList.Count - 1
                    For i As Integer = 0 To complexList.Count - 1
                        WriteComplexValues(tagFieldInfo.Children(complexList(i)), eWriter, blockList(f))
                    Next
                Next
        End Select
    End Sub

    Public Shared Function BuildTagTypeDictionary() As Dictionary(Of String, Type)

        Dim baseType As Type = GetType(HaloTag)
        Dim assembly As Assembly = baseType.Assembly

        Dim tagList As IEnumerable(Of Type) = From t In assembly.GetTypes()
                                              Where t.IsSubclassOf(baseType)
                                              Select t
        Dim tagDictionary As New Dictionary(Of String, Type)
        For Each t As Type In tagList
            Dim tagAttrib As TagAttribute = t.GetCustomAttribute(Of TagAttribute)
            tagDictionary.Add(tagAttrib.FourCC, t)
        Next
        Return tagDictionary
    End Function

    Public Shared Function BuildExtensionDictionary() As Dictionary(Of String, String)

        Dim baseType As Type = GetType(HaloTag)
        Dim assembly As Assembly = baseType.Assembly

        Dim tagList As IEnumerable(Of Type) = From t In assembly.GetTypes()
                                              Where t.IsSubclassOf(baseType)
                                              Select t
        Dim tagDictionary As New Dictionary(Of String, String)
        For Each t As Type In tagList
            Dim tagAttrib As TagAttribute = t.GetCustomAttribute(Of TagAttribute)
            tagDictionary.Add(tagAttrib.FourCC, tagAttrib.Extension)
        Next
        Return tagDictionary
    End Function

    Public Shared Function BuildTagDefinitionDictionary() As Dictionary(Of String, List(Of TagDefinition))

        Dim baseType As Type = GetType(HaloTag)
        Dim assembly As Assembly = baseType.Assembly

        Dim tagList As IEnumerable(Of Type) = From t In assembly.GetTypes()
                                              Where t.IsSubclassOf(baseType)
                                              Select t
        Dim tagDictionary As New Dictionary(Of String, List(Of TagDefinition))
        For Each t As Type In tagList
            Dim tagAttrib As TagAttribute = t.GetCustomAttribute(Of TagAttribute)
            Dim Children As New List(Of TagDefinition)
            Dim blockFieldInfo() As FieldInfo = t.GetFields(BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.Public)
            For x As Integer = 0 To blockFieldInfo.Length - 1
                Children.Add(New TagDefinition(blockFieldInfo(x)))
            Next
            tagDictionary.Add(tagAttrib.FourCC, Children)
        Next
        Return tagDictionary
    End Function

    Public Class TagDefinition
        Public FieldType As TagFieldAttribute.FieldType
        Public FieldSize As Integer
        Public Field As FieldInfo
        Public ChildType As Type
        Public Children As List(Of TagDefinition)
        Sub New(fInfo As FieldInfo)
            Field = fInfo
            Dim fieldAttrib As TagFieldAttribute = fInfo.GetCustomAttribute(Of TagFieldAttribute)
            FieldType = fieldAttrib.Type
            FieldSize = fieldAttrib.Length
            If FieldType = TagFieldAttribute.FieldType.Block Then
                Children = New List(Of TagDefinition)
                ChildType = fInfo.FieldType.GenericTypeArguments(0)
                Dim blockFieldInfo() As FieldInfo = ChildType.GetFields(BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.Public)
                For x As Integer = 0 To blockFieldInfo.Length - 1
                    Children.Add(New TagDefinition(blockFieldInfo(x)))
                Next
            End If
        End Sub
    End Class
End Class
